﻿using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Soap;
using Accord.Statistics.Models.Markov;
using PMatch.Formats.SequenceSample;

namespace PMatch
{
	/// <summary>
	/// Abstraccion de un clasificador binario usando modelos ocultos de markov
	/// </summary>
	public interface IMarkovModel
	{
		void Train(samples samples);
		bool Evaluate(sample sample);
		int States { get; }
		double Rtol { get; }
	}

	class SerializerHelper
	{
		/// <summary>
		/// Serializa un objeto y lo escribe en el archivo indicado
		/// </summary>		
		public static void Serialize<T>(T obj, string filename)
		{
			var fmt = new SoapFormatter();
			Stream stream = null;
			try
			{
				stream = new FileStream(filename, FileMode.Create);
				fmt.Serialize(stream, obj);
				stream.Close();
			}
			finally
			{
				if (stream != null) stream.Dispose();
			}
		}

		/// <summary>
		/// Deserializa un objeto leido desde el archivo indicado
		/// </summary>		
		public static T Deserialize<T>(string filename)
		{
			var fmt = new SoapFormatter();
			Stream stream = null;
			try
			{
				stream = new FileStream(filename, FileMode.Open);
				return (T)fmt.Deserialize(stream);
			}
			finally
			{
				if (stream != null) stream.Dispose();
			}
		}
	}

	/// <summary>
	/// Aprende un patron para reconocer muestras negativas
	/// Obtiene el maximo indicador para las muestras positivas y lo usa como
	/// umbral
	/// </summary>
	[Serializable]
	public class MarkovModel3 : IMarkovModel
	{
		public MarkovModel3(int states, double rtol)
		{
			States = states;
			Rtol = rtol;
			model = new HiddenMarkovModel(BioUtil.AminoacidAlphabet.Length, States);
		}

		public void Train(samples samples)
		{
			var pos = samples.positive.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			var neg = samples.negative.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			model.Learn(neg, Rtol);
			var max = pos.Max(x => model.Evaluate(x));
			Threshold = max; // (max - min) / 2 + min;
		}

		public bool Evaluate(sample sample)
		{
			var data = BioUtil.ToIntegerSequence(sample.data);
			return model.Evaluate(data) <= Threshold;
		}

		public int States { get; private set; }
		public double Rtol { get; private set; }
		public double Threshold { get; private set; }

		HiddenMarkovModel model;
	}

	/// <summary>
	/// Aprende un patron para reconocer muestras positivas
	/// Obtiene el minimo indicador para las muestras positivas y lo usa como
	/// umbral
	/// </summary>
	[Serializable]
	public class MarkovModel2 : IMarkovModel
	{
		public MarkovModel2(int states, double rtol)
		{
			States = states;
			Rtol = rtol;
			model = new HiddenMarkovModel(BioUtil.AminoacidAlphabet.Length, States);
		}

		public void Train(samples samples)
		{
			var pos = samples.positive.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			//var neg = samples.negative.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			model.Learn(pos, Rtol);
			var min = pos.Min(x => model.Evaluate(x));
			Threshold = min;
		}

		public bool Evaluate(sample sample)
		{
			var data = BioUtil.ToIntegerSequence(sample.data);
			return model.Evaluate(data) >= Threshold;
		}

		public int States { get; private set; }
		public double Rtol { get; private set; }
		public double Threshold { get; private set; }

		HiddenMarkovModel model;
	}


	[Serializable]
	public class MarkovModel : IMarkovModel
	{
		public MarkovModel(int states, double rtol, HMMType type)
		{
			States = states;
			Rtol = rtol;
			Type = type;
			var tt = type == HMMType.Ergodic ? HiddenMarkovModelType.Ergodic : HiddenMarkovModelType.Forward;
			pmodel = new HiddenMarkovModel(BioUtil.AminoacidAlphabet.Length, States, tt);
			nmodel = new HiddenMarkovModel(BioUtil.AminoacidAlphabet.Length, States, tt);
		}

		public void Train(samples samples)
		{
			var pos = samples.positive.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			var neg = samples.negative.Select(x => BioUtil.ToIntegerSequence(x)).ToArray();
			pmodel.Learn(pos, Rtol);
			nmodel.Learn(neg, Rtol);
		}

		public bool Evaluate(sample sample)
		{
			var data = BioUtil.ToIntegerSequence(sample.data);
			return pmodel.Evaluate(data) > nmodel.Evaluate(data);
		}

		public void SaveToFile(string filename)
		{
			SerializerHelper.Serialize(this, filename);
		}

		public static MarkovModel LoadFromFile(string filename)
		{
			return SerializerHelper.Deserialize<MarkovModel>(filename);
		}

		public int States { get; private set; }
		public double Rtol { get; private set; }
		public HMMType Type { get; private set; }

		HiddenMarkovModel pmodel;
		HiddenMarkovModel nmodel;
	}

	public enum HMMType
	{
		Ergodic,
		Forward,
	}
}
